#include <stdio.h>
#include <sys/queue.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <evhttp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/stat.h>

#include "m.h"

unsigned long long *head,*tail,*total_item_num;
const int max_item_size=4096;
unsigned int max_map_size=1024*1024*1024;
void *m;

static void en_queue(struct evhttp_request *r,void *d)
{
	int ret=0;
	struct evbuffer *evb=evbuffer_new();
	const char *str=r->uri+5;
	if(str)
	{
		//fprintf(stderr,"head : %llu , tail : %llu\n",(unsigned long long)*head,(unsigned long long)*tail);
		if( (*head - *tail) > (max_map_size - 3*sizeof(long long) - max_item_size ) || strlen(str) > max_item_size - 3*sizeof(int))
		{
			evbuffer_add_printf(evb,"%s","ERR_STORE");
		}
		else
		{
			if( max_map_size - *head % max_map_size < max_item_size )
			{
				*head+=(max_map_size - *head % max_map_size) + 3*sizeof(long long);
			}
			if ((ret = write_cell(m+(*head % max_map_size),strlen(str),0,0,(void *)str)) > 0)
			{
				*head+=ret;
				evbuffer_add_printf(evb,"%s","STORED");
				(*total_item_num)++;
			}
			else
			{
				evbuffer_add_printf(evb,"%s","NOT_STORED");
			}
		}
	}
	else
	{
		evbuffer_add_printf(evb,"%s","ERR_PARM");
	}
	evhttp_send_reply(r,HTTP_OK,"OK",evb);	
	evbuffer_free(evb);
}

static void out_queue(struct evhttp_request *r,void *d)
{
	int flag1,flag2,size,ret;
	void *data;
	struct evbuffer *evb=evbuffer_new();
	if((max_map_size - (*tail) % max_map_size < max_item_size) && (*head > *tail) )
	{
		*tail+=(max_map_size - *tail % max_map_size)+3*sizeof(long long);
	}
	//fprintf(stderr,"head : %llu , tail : %llu\n",(unsigned long long)*head,(unsigned long long)*tail);
	if ( (*head > *tail) &&
		(ret = read_cell(m+(*tail % max_map_size),&size,&flag1,&flag2,&data)) > 0)
	{
		*tail+=ret;
		evbuffer_add(evb,data,size);
		(*total_item_num)--;
	}
	else
	{
		evbuffer_add_printf(evb,"%s","ERR_OUT");
	}
	evhttp_send_reply(r,HTTP_OK,"OK",evb);	
	evbuffer_free(evb);
}
static void stat_queue(struct evhttp_request *r,void *d)
{
	struct evbuffer *evb=evbuffer_new();
	evbuffer_add_printf(evb,"head -> %ld,tail -> %ld,total -> %llu \n",(long )(*head % max_map_size),(long)(*tail % max_map_size),*total_item_num);
	evhttp_send_reply(r,HTTP_OK,"OK",evb);
	evbuffer_free(evb);
}
void usage()
{
	printf("p: listen port,default 9009\n");
	printf("l: listen address,default '0.0.0.0'\n");
	printf("f: path to dbfile,default 'dbfile' in current directory\n");
	printf("m: size (MB) of dbfile,default 1024,max 1024\n");
	printf("h: show this help information\n");
	printf("d: become a daemon,default no\n");
	printf("C: create or overwrite the dbfile,default no\n");
}
int main(int argc,char **argv)
{
	char c;
	int port=9009;
	char *db_file="dbfile";
	char *interface="0.0.0.0";
	struct in_addr addr;
	int daemonize=0;
	int create_db=0;
	while ((c = getopt(argc, argv, "p:m:f:l:dhC")) != -1) {
		switch (c) {
		case 'p':
			port = atoi(optarg);
			break;
		case 'm':
			max_map_size=atoi(optarg) *1024*1024;
			if(max_map_size > 1024*1024*1024) max_map_size=1024*1024*1024;
			break;
		case 'h':
			usage();
			exit(0);
			break;
		case 'f':
			db_file = optarg;
			break;
		case 'l':
			if (!inet_aton(optarg, &addr)) {
				fprintf(stderr, "Illegal address: %s\n", optarg);
				return -1;
			}
			interface = optarg;
			break;
		case 'd':
			daemonize = 1;
			break;
		case 'C':
			create_db = 1;
			break;
		default:
			fprintf(stderr, "Illegal argument \"%c\"\n", c);
			return 1;
		}
	}
	if(daemonize) daemon(1,1);
	struct sigaction sa;
	sa.sa_handler=SIG_IGN;
	sa.sa_flags=0;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGPIPE, &sa, 0);
	if(create_db == 0)
	{
		struct stat st;
		stat(db_file,&st);
		max_map_size=st.st_size;
	}
	m=open_db(db_file,max_map_size,create_db);
	if(!m) return -1;
	struct event_base *base=event_init();
	struct evhttp *eh=evhttp_new(base);
	evhttp_bind_socket(eh,interface,port);
	evhttp_set_cb(eh,"/get",out_queue,NULL);
	evhttp_set_cb(eh,"/add",en_queue,NULL);
	evhttp_set_cb(eh,"/stat",stat_queue,NULL);
	event_dispatch();
	evhttp_free(eh);
	return 0;
}
